/**
 * TCP粘包处理
 */

import { TcpNetConnectOpts } from "net";
import { createConnection, createServer, ListenOptions, Server, Socket, TcpSocketConnectOpts } from "net";
import AvoidNullCallable from "../avoidnullcall/avoidnullcallable";

export default class Tunnel extends AvoidNullCallable{
    private socket: Socket;
    private type: boolean;


    private recivecb: (data: Buffer) => void;
    private connectcb: (socket: Socket) => void;
    private dataDraincb: () => void;

    public reciveBuffer: Buffer;

    private socketUsed: boolean;

    private fileIndex: number;

    /**
     * 
     * @param type true为服务器, false为客户端
     * @param options 根据模式指定监听选项还是连接选项
     */
    constructor(type:boolean, options: any, listencb?: () => void)
    {
        super();

        this.type = type;
        this.fileIndex = 0;
        this.socketUsed = false;

        if(this.type)
        {
            //服务器
            let server = createServer();
            server.listen(options, () => {
                this.avoidNullCall(listencb)();
            });
            server.on("connection", (socket: Socket) => {
                if(this.socket != null)
                {
                    socket.destroy();
                    return;
                }
                
                this.socket = socket;
                this.bindReciveData();
                this.socketUsed = true;
                this.avoidNullCall(this.connectcb)(socket);
                socket.on("drain", this.avoidNullCall(this.dataDraincb));
            });
        }
        else
        {
            //客户端
            //let client = createConnection(options, () => {});
            this.socket = new Socket();
            this.bindReciveData();
            this.socket.connect(options, () => {
                this.socketUsed = true;
                this.avoidNullCall(this.connectcb)(this.socket);
                this.socket.on("drain", this.avoidNullCall(this.dataDraincb));
            });

        }

    }
    regBlockRecive(cb: (data: Buffer) => void)
    {
        this.recivecb = cb;
    }
    writeBlock(data:Buffer):boolean {
        let bufferlength = data.length;
        let length_buffer = Buffer.from([0, 0, 0, 0]);
        length_buffer.writeUInt32LE(bufferlength);

        let buffer_send = Buffer.concat([length_buffer, data]);
        return this.socket.write(buffer_send);
    }
    regConnected(cb: (socket: Socket) => void)
    {
        this.connectcb = cb;
    }
    regDataDrain(cb: () => void)
    {
        this.dataDraincb = cb;
    }
    getSocket():Socket {
        return this.socket;
    }
    getStatus(): boolean {
        // if(this.getSocket == null) return false;
        // return true;
        return this.socketUsed;
    }
    private bindReciveData() {
        this.socket.on("data", (data: Buffer) => {
            this.handleData(data);
        });
    }
    private handleData(data: Buffer) {
        let d1:Buffer = data;

        if(this.reciveBuffer != null)
        {
            d1 = Buffer.concat([this.reciveBuffer, d1]);
        }

        let packet_length: number;

        //根据当前包头定义的包长度，截取剩余部分，或者等待剩余部分，然后调用recivecb
        while(true)
        {
            if(d1.length <= 4)
            {
                this.reciveBuffer = d1;
                break;
            }
            packet_length = d1.readUInt32LE(0);

            if(packet_length == d1.length - 4)
            {
                this.reciveBuffer = null;
                this.avoidNullCall(this.recivecb)(d1.slice(4, d1.length));
                break;
            }else {
                if(packet_length > d1.length - 4) //没接收完
                {
                    this.reciveBuffer = d1;
                    break;
                }
                else if(packet_length < d1.length - 4) //接过头了
                {
                    //有可能多次接过头，则循环处理
                    let left = d1.slice(4, packet_length + 4);
                    let right = d1.slice(packet_length + 4, d1.length);

                    this.avoidNullCall(this.recivecb)(left);
                    this.reciveBuffer = right;
                    d1 = right;
                }
            }
        }
    }

}